Skip to content

Feature/companies list mui#910

Open
tomrndom wants to merge 8 commits into
masterfrom
feature/companies-list-mui
Open

Feature/companies list mui#910
tomrndom wants to merge 8 commits into
masterfrom
feature/companies-list-mui

Conversation

@tomrndom

@tomrndom tomrndom commented May 4, 2026

Copy link
Copy Markdown

ref: https://app.clickup.com/t/86b9n7p5w

Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com

Summary by CodeRabbit

  • New Features

    • Added color picker for company profiles
    • Introduced dialog-based company creation and editing interface
    • Added logo upload functionality for companies
    • Enhanced sponsorship management with project support tracking
  • Improvements

    • Replaced alert dialogs with snackbar notifications for errors and confirmations
    • Redesigned company list with improved search and filtering capabilities
    • Enhanced loading state management across company operations
    • Added "no results" messaging for empty searches

@coderabbitai

coderabbitai Bot commented May 4, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9d7a5ff7-0d8f-4883-ad34-4fc79166b6ee

📥 Commits

Reviewing files that changed from the base of the PR and between f2aa623 and 13d54a4.

📒 Files selected for processing (3)
  • src/actions/company-actions.js
  • src/pages/companies/company-list-page.js
  • src/pages/companies/components/company-dialog.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/actions/company-actions.js
  • src/pages/companies/company-list-page.js
  • src/pages/companies/components/company-dialog.js

📝 Walkthrough

Walkthrough

Refactors the company list page from a class component to a functional MUI-based page with a dialog-driven create/edit flow via a new CompanyDialog. Company Redux actions migrate from authErrorHandler/showMessage to snackbarErrorHandler/snackbarSuccessHandler with stopLoading moved to .finally(). Reducers update the companies state shape to an array. MuiFormikAsyncAutocomplete gains a defaultOptions prop for client-side filtering.

Changes

Company Management MUI Refactor

Layer / File(s) Summary
Redux state shape and action notification migration
src/reducers/companies/company-list-reducer.js, src/reducers/companies/company-reducer.js, src/actions/company-actions.js
company-list-reducer initializes companies as an array and adds perPage to REQUEST_COMPANIES. company-reducer sets DEFAULT_ENTITY.color to "" and refactors switch cases to direct returns. All company-actions async thunks switch from authErrorHandler/showSuccessMessage to snackbarErrorHandler/snackbarSuccessHandler, with stopLoading moved to .finally(); the create branch no longer navigates to /app/companies on success.
MuiFormikAsyncAutocomplete defaultOptions extension
package.json, src/components/mui/formik-inputs/mui-formik-async-select.js
Adds mui-color-input dependency. Extends MuiFormikAsyncAutocomplete with a defaultOptions prop that disables remote searchTerm updates and substitutes client-side case-insensitive opt.label filtering; without defaultOptions, remote fetch and MUI default behavior are preserved.
New CompanyDialog component
src/pages/companies/components/company-dialog.js, src/i18n/en.json
Introduces CompanyDialog: a Formik-backed MUI Dialog with sponsor membership constants, logo normalization, and a memoized ColorPickerField. Handles logo upload/remove, sponsorship project/type selection and addition, sponsorship deletion with confirmation, rich-text fields, async country autocomplete, and logo upload inputs. Conditionally renders OpenStack-only sponsorship UI with an existing sponsorships table. Adds select_project_first and no_options i18n placeholders.
CompanyListPage class→functional refactor
src/pages/companies/company-list-page.js, src/i18n/en.json
Rewrites CompanyListPage as a functional component with hook-managed dialog state. Replaces SweetAlert2/pagination/legacy table with MuiTable, SearchInput, and Grid2. Wires dialog-driven edit/create/save/delete handlers and mounts getSponsoredProjects for the OpenStack client. Updates mapStateToProps and connected action set. Adds delete_company_warning (with {name}) and no_results i18n strings.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant CompanyListPage
  participant CompanyDialog
  participant company-actions
  participant API as REST API
  participant Snackbar as snackbarHandler

  rect rgba(33, 150, 243, 0.5)
    Note over User,CompanyDialog: Edit flow
    User->>CompanyListPage: click Edit on row
    CompanyListPage->>company-actions: getCompany(id)
    company-actions->>API: GET /companies/:id
    API-->>company-actions: company entity
    company-actions-->>CompanyListPage: currentCompany updated in Redux
    CompanyListPage->>CompanyDialog: open with currentCompany
  end

  rect rgba(76, 175, 80, 0.5)
    Note over User,Snackbar: Save flow
    User->>CompanyDialog: submit form
    CompanyDialog->>CompanyListPage: onSave(entity)
    CompanyListPage->>company-actions: saveCompany(entity)
    company-actions->>API: PUT or POST /companies
    API-->>company-actions: success
    company-actions->>Snackbar: snackbarSuccessHandler
    company-actions->>company-actions: stopLoading() via .finally()
    CompanyListPage->>CompanyDialog: close dialog
    CompanyListPage->>company-actions: getCompanies() refresh
  end

  rect rgba(244, 67, 54, 0.5)
    Note over User,Snackbar: Delete flow
    User->>CompanyListPage: confirm delete via MuiTable dialog
    CompanyListPage->>company-actions: deleteCompany(id)
    company-actions->>API: DELETE /companies/:id
    API-->>company-actions: success
    company-actions->>Snackbar: snackbarSuccessHandler
    company-actions->>company-actions: stopLoading() via .finally()
    CompanyListPage->>company-actions: getCompanies() refresh
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested reviewers

  • smarcet

Poem

🐇 A dialog blooms where old classes once stood,
MUI tables replacing the pagination wood.
Snackbars now carry the errors away,
While CompanyDialog brightens the day.
The rabbit hops proud through refactored code,
Sponsorships managed on a colorful road! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'Feature/companies list mui' is vague and generic; it lacks specificity about what the feature accomplishes and uses unclear shorthand 'mui'. Revise the title to be more descriptive, such as 'Refactor companies list page to use Material-UI components' to clearly convey the main change.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/companies-list-mui

Comment @coderabbitai help to get the list of available commands and usage tips.

tomrndom added 6 commits June 17, 2026 12:50
…er and actions

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@tomrndom tomrndom force-pushed the feature/companies-list-mui branch from c32be01 to 4616df3 Compare June 17, 2026 15:51
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@tomrndom tomrndom marked this pull request as ready for review June 17, 2026 15:57

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/actions/company-actions.js (2)

210-219: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ensure attachLogo clears loading state when company creation fails.

Line 197 starts loading, but in the create-before-upload branch (Line 210-219), a failure in postRequest never reaches uploadFile (where stopLoading lives), leaving loading stuck.

💡 Suggested fix
   } else {
-    return postRequest(
+    return postRequest(
       createAction(UPDATE_COMPANY),
       createAction(COMPANY_ADDED),
       `${window.API_BASE_URL}/api/v1/companies`,
       normalizedEntity,
       snackbarErrorHandler,
       entity
-    )(params)(dispatch).then((payload) => {
-      dispatch(uploadFile(payload.response, file));
-    });
+    )(params)(dispatch)
+      .then((payload) => dispatch(uploadFile(payload.response, file)))
+      .catch((err) => {
+        dispatch(stopLoading());
+        throw err;
+      });
   }
 };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/company-actions.js` around lines 210 - 219, The postRequest call
in the attachLogo function (lines 210-219) only handles the success case via the
.then() handler which calls uploadFile and its associated stopLoading action,
but does not handle errors from postRequest. Add a .catch() handler to the
promise chain that dispatches the stopLoading action when postRequest fails,
ensuring the loading state is cleared regardless of whether the request succeeds
or fails.

155-191: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return the request promise from saveCompany.

Line 156 and Line 174 start async requests but do not return them. The thunk resolves early, so callers cannot await real completion (which can break dialog isSaving/post-save sequencing).

💡 Suggested fix
   if (entity.id) {
-    putRequest(
+    return putRequest(
       createAction(UPDATE_COMPANY),
       createAction(COMPANY_UPDATED),
       `${window.API_BASE_URL}/api/v1/companies/${entity.id}`,
       normalizedEntity,
       snackbarErrorHandler,
       entity
     )(params)(dispatch)
       .then(() => {
         dispatch(
           snackbarSuccessHandler({
             title: T.translate("general.success"),
             html: T.translate("edit_company.company_saved")
           })
         );
       })
       .finally(() => dispatch(stopLoading()));
   } else {
-    postRequest(
+    return postRequest(
       createAction(UPDATE_COMPANY),
       createAction(COMPANY_ADDED),
       `${window.API_BASE_URL}/api/v1/companies`,
       normalizedEntity,
       snackbarErrorHandler,
       entity
     )(params)(dispatch)
       .then(() => {
         dispatch(
           snackbarSuccessHandler({
             title: T.translate("general.success"),
             html: T.translate("edit_company.company_created")
           })
         );
       })
       .finally(() => dispatch(stopLoading()));
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/company-actions.js` around lines 155 - 191, The saveCompany
function does not return the promises from the async requests, causing the thunk
to resolve before the actual requests complete. Add return statements before
both the putRequest call (in the if block when entity.id exists) and the
postRequest call (in the else block) to return the promise chains, allowing
callers to properly await the completion of the save operations and maintain
correct sequencing for dialog state management.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/components/mui/formik-inputs/mui-formik-color-input.js`:
- Line 8: The localValue state in the component is only initialized once from
field.value using useState, so it does not update when the Formik field value
changes externally (such as during form resets). Add a useEffect hook with
field.value as a dependency that calls setLocalValue whenever field.value
changes. This will ensure the color picker always displays the current Formik
field value even when it is updated externally.

In `@src/pages/companies/company-list-page.js`:
- Around line 78-80: The useState hook at the initial companies fetch is being
misused with a callback and dependency array, but useState ignores the second
argument entirely and is not intended for side effects. Replace the useState
call with useEffect to properly handle the side effect of calling getCompanies
on component mount, similar to how getSponsoredProjects is correctly implemented
elsewhere in the file. Move the getCompanies invocation inside the useEffect
function body and pass an empty dependency array to ensure it executes only on
initial component mount.

In `@src/pages/companies/components/company-dialog.js`:
- Around line 96-114: The `country` field is missing from the Formik
`initialValues` object in the company-dialog.js file, even though a country
autocomplete input exists in the form. Add the missing `country` field to the
initialValues object alongside the other fields like `city` and `state`, using
the pattern `country: initialEntity?.country ?? ""` to ensure the country value
is properly initialized and preserved when the form is submitted via
formik.values.
- Around line 174-184: The handleDeleteSponsorship function is not awaiting the
Promise returned by showConfirmDialog, which causes the confirm variable to hold
a Promise object instead of the boolean result. Since a Promise object is always
truthy, the if (confirm) check will always be true and onDeleteSponsorship will
execute immediately without waiting for user confirmation. Make the function
async by adding the async keyword to its declaration, then add await before the
showConfirmDialog call when assigning to confirm, so the code waits for the
user's response before executing onDeleteSponsorship.
- Around line 155-161: The handleAddSponsorshipType function calls
onAddSponsorship with incorrect argument order. The current order passes
(companyId, projectId, sponsorshipTypeId), but the function expects (projectId,
sponsorshipTypeId, entity) where the company ID must be included in the entity
object as a property. Reorder the arguments so selectedSponsoredProject is
passed first, selectedSponsorShipType second, and create an entity object as the
third argument that contains the company ID from formik.values.id.

In `@src/reducers/companies/company-reducer.js`:
- Around line 104-111: The code assumes that the find() call searching through
project_sponsorships will always return a match, but when no
supporting_companies matches the given supportingCompanyId, the variable f will
be undefined. When f is undefined, the subsequent filter operation trying to
access f.id will throw an error. Add a guard condition to check that f is
defined and truthy before attempting to filter project_sponsorships by f.id. If
f is undefined, return the current state unchanged or handle the case
appropriately instead of proceeding with the filter.
- Around line 63-69: The reducer's switch statement for the LOGOUT_USER case is
missing null safety for the payload object. In the destructuring assignment
where type and payload are extracted from the action parameter, add a default
value of an empty object to the payload variable to handle cases where
action.payload is undefined. Then, replace the unsafe hasOwnProperty call on
payload with Object.prototype.hasOwnProperty.call(payload, "persistStore") to
safely check for the persistStore property without throwing a TypeError when
payload is undefined.

---

Outside diff comments:
In `@src/actions/company-actions.js`:
- Around line 210-219: The postRequest call in the attachLogo function (lines
210-219) only handles the success case via the .then() handler which calls
uploadFile and its associated stopLoading action, but does not handle errors
from postRequest. Add a .catch() handler to the promise chain that dispatches
the stopLoading action when postRequest fails, ensuring the loading state is
cleared regardless of whether the request succeeds or fails.
- Around line 155-191: The saveCompany function does not return the promises
from the async requests, causing the thunk to resolve before the actual requests
complete. Add return statements before both the putRequest call (in the if block
when entity.id exists) and the postRequest call (in the else block) to return
the promise chains, allowing callers to properly await the completion of the
save operations and maintain correct sequencing for dialog state management.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0f161bb2-ad2c-464d-98fc-783e2d30d6b7

📥 Commits

Reviewing files that changed from the base of the PR and between 5a40170 and f2aa623.

⛔ Files ignored due to path filters (1)
  • yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (9)
  • package.json
  • src/actions/company-actions.js
  • src/components/mui/formik-inputs/mui-formik-async-select.js
  • src/components/mui/formik-inputs/mui-formik-color-input.js
  • src/i18n/en.json
  • src/pages/companies/company-list-page.js
  • src/pages/companies/components/company-dialog.js
  • src/reducers/companies/company-list-reducer.js
  • src/reducers/companies/company-reducer.js


const MuiFormikColorInput = ({ name, ...rest }) => {
const [field, meta, helpers] = useField(name);
const [localValue, setLocalValue] = useState(field.value || "#000000");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "mui-formik-color-input.js" -type f

Repository: fntechgit/summit-admin

Length of output: 127


🏁 Script executed:

cat -n ./src/components/mui/formik-inputs/mui-formik-color-input.js

Repository: fntechgit/summit-admin

Length of output: 1653


🏁 Script executed:

# Check if there are other similar color input components or formik input components
find ./src/components/mui/formik-inputs -type f -name "*.js" | head -20

Repository: fntechgit/summit-admin

Length of output: 1284


🏁 Script executed:

# Check how this component is used in the codebase to understand reset/reinitialize patterns
rg "MuiFormikColorInput" --type js -A 2 -B 2 | head -50

Repository: fntechgit/summit-admin

Length of output: 1103


🏁 Script executed:

# Search more broadly for usage of this component (case-insensitive)
rg "formik-color-input" --type js -i | head -20

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check if there's a pattern of how other components handle Formik field changes
rg "useField" ./src/components/mui/formik-inputs -A 5 | head -100

Repository: fntechgit/summit-admin

Length of output: 7868


🏁 Script executed:

# Check if Formik's useField updates when field.value changes externally
# Also check for form reset patterns in the codebase
rg "formik|resetForm|reset" ./src/components/mui/formik-inputs -i | grep -E "(reset|Form)" | head -20

Repository: fntechgit/summit-admin

Length of output: 2021


🏁 Script executed:

# Search for resetForm usage in the codebase
rg "resetForm|\.reset\(" --type js -B 3 -A 3 | head -60

Repository: fntechgit/summit-admin

Length of output: 4766


🏁 Script executed:

# Check if there are examples of useEffect with field.value dependency in other formik input components
rg "useEffect.*field\." ./src/components/mui/formik-inputs --type js -A 5 -B 2

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Check if there are any useEffect patterns in formik inputs at all
rg "useEffect" ./src/components/mui/formik-inputs --type js -A 3 -B 2

Repository: fntechgit/summit-admin

Length of output: 6376


🏁 Script executed:

# Let me verify the exact issue: check if useState initializer runs only once
# and if field.value changes are reflected
rg "useState.*field\.value" ./src/components/mui/formik-inputs --type js -B 2 -A 5

Repository: fntechgit/summit-admin

Length of output: 865


Sync local color state when Formik value changes externally.

The localValue state is initialized only once from field.value, so form resets or external Formik value updates leave the picker displaying stale data. Add a useEffect to synchronize the local state whenever the Formik field value changes.

Proposed fix
-import React, { useRef, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
@@
   const [localValue, setLocalValue] = useState(field.value || "`#000000`");
   const isDirtyRef = useRef(false);
+
+  useEffect(() => {
+    setLocalValue(field.value || "`#000000`");
+  }, [field.value]);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/mui/formik-inputs/mui-formik-color-input.js` at line 8, The
localValue state in the component is only initialized once from field.value
using useState, so it does not update when the Formik field value changes
externally (such as during form resets). Add a useEffect hook with field.value
as a dependency that calls setLocalValue whenever field.value changes. This will
ensure the color picker always displays the current Formik field value even when
it is updated externally.

Comment thread src/pages/companies/company-list-page.js Outdated
Comment on lines +96 to +114
initialValues: {
id: initialEntity?.id ?? 0,
name: initialEntity?.name ?? "",
url: initialEntity?.url ?? "",
contact_email: initialEntity?.contact_email ?? "",
member_level: initialEntity?.member_level ?? "",
color: initialEntity?.color ?? "",
admin_email: initialEntity?.admin_email ?? "",
city: initialEntity?.city ?? "",
state: initialEntity?.state ?? "",
industry: initialEntity?.industry ?? "",
products: initialEntity?.products ?? "",
contributions: initialEntity?.contributions ?? "",
description: initialEntity?.description ?? "",
overview: initialEntity?.overview ?? "",
commitment: initialEntity?.commitment ?? "",
logo: initialEntity?.logo ?? "",
big_logo: initialEntity?.big_logo ?? ""
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/pages/companies/components/company-dialog.js | sed -n '90,120p'

Repository: fntechgit/summit-admin

Length of output: 1570


🏁 Script executed:

cat -n src/pages/companies/components/company-dialog.js | sed -n '300,325p'

Repository: fntechgit/summit-admin

Length of output: 1309


country field is missing from Formik initialValues and will be lost on submit.

The form renders an autocomplete for name="country" (line 308), but country is not initialized in the Formik initialValues object (lines 96-114). When the form is submitted, valuesToSave = { ...formik.values } will exclude the country field, causing any edits to be discarded.

Proposed fix
     initialValues: {
       id: initialEntity?.id ?? 0,
       name: initialEntity?.name ?? "",
       url: initialEntity?.url ?? "",
       contact_email: initialEntity?.contact_email ?? "",
       member_level: initialEntity?.member_level ?? "",
       color: initialEntity?.color ?? "",
       admin_email: initialEntity?.admin_email ?? "",
       city: initialEntity?.city ?? "",
       state: initialEntity?.state ?? "",
+      country: initialEntity?.country ?? "",
       industry: initialEntity?.industry ?? "",
       products: initialEntity?.products ?? "",
       contributions: initialEntity?.contributions ?? "",
       description: initialEntity?.description ?? "",
       overview: initialEntity?.overview ?? "",
       commitment: initialEntity?.commitment ?? "",
       logo: initialEntity?.logo ?? "",
       big_logo: initialEntity?.big_logo ?? ""
     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/companies/components/company-dialog.js` around lines 96 - 114, The
`country` field is missing from the Formik `initialValues` object in the
company-dialog.js file, even though a country autocomplete input exists in the
form. Add the missing `country` field to the initialValues object alongside the
other fields like `city` and `state`, using the pattern `country:
initialEntity?.country ?? ""` to ensure the country value is properly
initialized and preserved when the form is submitted via formik.values.

Comment on lines +155 to +161
const handleAddSponsorshipType = () => {
if (!selectedSponsoredProject || !selectedSponsorShipType) return;
onAddSponsorship(
formik.values.id,
selectedSponsoredProject,
selectedSponsorShipType
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's find and read the file in question
find . -name "company-dialog.js" -type f

Repository: fntechgit/summit-admin

Length of output: 117


🏁 Script executed:

# Search for onAddSponsorship definition and usage
rg "onAddSponsorship" --type js -B 2 -A 10

Repository: fntechgit/summit-admin

Length of output: 6134


🏁 Script executed:

# Search for sponsorship-related action definitions or API calls
rg "sponsorship" --type js -i | head -50

Repository: fntechgit/summit-admin

Length of output: 4765


🏁 Script executed:

# Find the definition of saveSupportingCompany and addSponsoreProjectSponsorship
rg "saveSupportingCompany|addSponsoreProjectSponsorship" --type js -B 5 -A 15

Repository: fntechgit/summit-admin

Length of output: 17877


🏁 Script executed:

# Also search for the action creator definition
fd -type f -name "*action*" | grep -E "(company|sponsor)" | head -20

Repository: fntechgit/summit-admin

Length of output: 237


🏁 Script executed:

# Look for reducer or saga that handles sponsorship
rg "addSponsoreProjectSponsorship|saveSupportingCompany" --type js | head -20

Repository: fntechgit/summit-admin

Length of output: 1301


🏁 Script executed:

cat -n src/pages/companies/components/company-dialog.js | sed -n '150,165p'

Repository: fntechgit/summit-admin

Length of output: 599


🏁 Script executed:

# Also check what the entire file looks like to understand the full context
wc -l src/pages/companies/components/company-dialog.js

Repository: fntechgit/summit-admin

Length of output: 119


🏁 Script executed:

# Let me check the exact saveSupportingCompany implementation to see the full parameters
sed -n '1,50p' src/actions/sponsored-project-actions.js | grep -A 30 "export const saveSupportingCompany"

Repository: fntechgit/summit-admin

Length of output: 48


🏁 Script executed:

# Find the exact line where saveSupportingCompany is exported
rg "export const saveSupportingCompany" src/actions/sponsored-project-actions.js -A 40

Repository: fntechgit/summit-admin

Length of output: 1447


Fix argument order in sponsorship add call.

onAddSponsorship is called with (companyId, projectId, sponsorshipTypeId), but saveSupportingCompany expects (projectId, sponsorshipTypeId, entity). The API path requires projectId as the first parameter, and the company ID must be passed as part of the entity object.

Proposed fix
  const handleAddSponsorshipType = () => {
    if (!selectedSponsoredProject || !selectedSponsorShipType) return;
    onAddSponsorship(
-     formik.values.id,
      selectedSponsoredProject,
-     selectedSponsorShipType
+     selectedSponsorShipType,
+     { id: formik.values.id }
    );
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleAddSponsorshipType = () => {
if (!selectedSponsoredProject || !selectedSponsorShipType) return;
onAddSponsorship(
formik.values.id,
selectedSponsoredProject,
selectedSponsorShipType
);
const handleAddSponsorshipType = () => {
if (!selectedSponsoredProject || !selectedSponsorShipType) return;
onAddSponsorship(
selectedSponsoredProject,
selectedSponsorShipType,
{ id: formik.values.id }
);
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/pages/companies/components/company-dialog.js` around lines 155 - 161, The
handleAddSponsorshipType function calls onAddSponsorship with incorrect argument
order. The current order passes (companyId, projectId, sponsorshipTypeId), but
the function expects (projectId, sponsorshipTypeId, entity) where the company ID
must be included in the entity object as a property. Reorder the arguments so
selectedSponsoredProject is passed first, selectedSponsorShipType second, and
create an entity object as the third argument that contains the company ID from
formik.values.id.

Comment thread src/pages/companies/components/company-dialog.js Outdated
Comment on lines 63 to 69
const { type, payload } = action;
switch (type) {
case LOGOUT_USER:
{
// we need this in ce the token expired while editing the form
if (payload.hasOwnProperty("persistStore")) {
return state;
} else {
return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} };
}
// we need this in ce the token expired while editing the form
if (payload.hasOwnProperty("persistStore")) {
return state;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether LOGOUT_USER is ever dispatched without payload in repository code.
rg -nP --type=js -C2 'type\s*:\s*LOGOUT_USER'
rg -nP --type=js -C2 'createAction\(\s*LOGOUT_USER\s*\)'

Repository: fntechgit/summit-admin

Length of output: 2050


Guard LOGOUT_USER against missing payload.

Line 67 calls payload.hasOwnProperty(...) without first ensuring payload exists. Test cases dispatch LOGOUT_USER without a payload object (e.g., sponsor-form-items-list-reducer.test.js:60, badge-scans-list-reducer.test.js:71, show-pages-list-reducer.test.js:55), causing the reducer to throw TypeError and break state updates during logout.

Apply the suggested fix:

  1. Default payload to {} at destructuring.
  2. Use Object.prototype.hasOwnProperty.call(payload, "persistStore") for safe property checks.
💡 Suggested fix
-const companyReducer = (state = DEFAULT_STATE, action) => {
-  const { type, payload } = action;
+const companyReducer = (state = DEFAULT_STATE, action) => {
+  const { type, payload = {} } = action;
   switch (type) {
     case LOGOUT_USER:
       // we need this in case the token expired while editing the form
-      if (payload.hasOwnProperty("persistStore")) {
+      if (Object.prototype.hasOwnProperty.call(payload, "persistStore")) {
         return state;
       }
       return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { type, payload } = action;
switch (type) {
case LOGOUT_USER:
{
// we need this in ce the token expired while editing the form
if (payload.hasOwnProperty("persistStore")) {
return state;
} else {
return { ...state, entity: { ...DEFAULT_ENTITY }, errors: {} };
}
// we need this in ce the token expired while editing the form
if (payload.hasOwnProperty("persistStore")) {
return state;
}
const { type, payload = {} } = action;
switch (type) {
case LOGOUT_USER:
// we need this in case the token expired while editing the form
if (Object.prototype.hasOwnProperty.call(payload, "persistStore")) {
return state;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reducers/companies/company-reducer.js` around lines 63 - 69, The
reducer's switch statement for the LOGOUT_USER case is missing null safety for
the payload object. In the destructuring assignment where type and payload are
extracted from the action parameter, add a default value of an empty object to
the payload variable to handle cases where action.payload is undefined. Then,
replace the unsafe hasOwnProperty call on payload with
Object.prototype.hasOwnProperty.call(payload, "persistStore") to safely check
for the persistStore property without throwing a TypeError when payload is
undefined.

Comment on lines +104 to 111
const f = project_sponsorships.find((ps) => {
const e = ps.supporting_companies.find(
(sp) => sp.id == payload.supportingCompanyId
);
return e;
});
project_sponsorships = project_sponsorships.filter((e) => e.id != f.id);
return {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Avoid dereferencing f.id when no sponsorship match exists.

Line 110 assumes find(...) always succeeds. A stale or already-deleted supportingCompanyId makes f undefined and throws in the reducer.

💡 Suggested fix
-      const f = project_sponsorships.find((ps) => {
+      const f = project_sponsorships.find((ps) => {
         const e = ps.supporting_companies.find(
           (sp) => sp.id == payload.supportingCompanyId
         );
         return e;
       });
+      if (!f) return state;
       project_sponsorships = project_sponsorships.filter((e) => e.id != f.id);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const f = project_sponsorships.find((ps) => {
const e = ps.supporting_companies.find(
(sp) => sp.id == payload.supportingCompanyId
);
return e;
});
project_sponsorships = project_sponsorships.filter((e) => e.id != f.id);
return {
const f = project_sponsorships.find((ps) => {
const e = ps.supporting_companies.find(
(sp) => sp.id == payload.supportingCompanyId
);
return e;
});
if (!f) return state;
project_sponsorships = project_sponsorships.filter((e) => e.id != f.id);
return {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/reducers/companies/company-reducer.js` around lines 104 - 111, The code
assumes that the find() call searching through project_sponsorships will always
return a match, but when no supporting_companies matches the given
supportingCompanyId, the variable f will be undefined. When f is undefined, the
subsequent filter operation trying to access f.id will throw an error. Add a
guard condition to check that f is defined and truthy before attempting to
filter project_sponsorships by f.id. If f is undefined, return the current state
unchanged or handle the case appropriately instead of proceeding with the
filter.

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review contra patrón popup/dialog (ticket #86bag2zk7)

El ticket #86bag2zk7 define el patrón canónico para popups en páginas de lista MUI. Esta PR introduce el mismo CompanyListPage + CompanyDialog pero desvía del patrón en 4 puntos críticos y tiene 2 bugs adicionales. Ver los comentarios inline para detalle específico.

sponsoredProjects
}) => {
const [companyPopup, setCompanyPopup] = useState(false);
const [isSaving, setIsSaving] = useState(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Anti-patrón: isSaving vive en el padre

El patrón del ticket es explícito: "isSaving lives in the popup, never in the parent".

isSaving debe moverse a CompanyDialog y eliminarse de CompanyListPage. El padre nunca debe controlar el estado de guardado del popup.

sortDir: orderDir
};

useState(() => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[MEDIUM] useState usado como useEffect

useState ignora su segundo argumento — el [] de dependencias no tiene efecto. La función se ejecuta como lazy initializer durante el render, lo cual es un efecto secundario en fase de render (prohibido en React y ejecutado dos veces en Strict Mode).

// ❌ actual
useState(() => { getCompanies(); }, []);

// ✅ correcto
useEffect(() => { getCompanies(); }, []);

setIsSaving(true);
saveCompany(entity)
.then(() => {
setCompanyPopup(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Anti-patrón: el padre cierra el dialog y no retorna la promesa

Dos deviaciones del patrón del ticket en este bloque:

  1. El padre cierra el dialog (setCompanyPopup(false)) — el patrón requiere que sea el popup quien llame onClose() en su propio .then().
  2. handleSave no retorna la promesa — el ticket requiere: "Parent's handleSave must RETURN the promise so the popup chain works". Sin return, el popup no puede encadenar .then(() => onClose()).
// ✅ correcto según el patrón
const handleSave = (entity) =>
  saveCompany(entity)
    .then(() => getCompanies(term, DEFAULT_CURRENT_PAGE, perPage, order, orderDir));
// El popup recibe este handleSave como onSave y llama onClose() en su propio .then()

{companyPopup && (
<CompanyDialog
entity={currentCompany}
isSaving={isSaving}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Prop drilling de isSaving al popup

El ticket lo lista como anti-patrón explícito: "Split onSaved + onClose props are the anti-pattern" y el equivalente aquí es pasar isSaving al popup en lugar de que el popup lo gestione internamente.

CompanyDialog debe tener su propio const [isSaving, setIsSaving] = useState(false) y no recibir isSaving como prop.

)(params)(dispatch).then(() => {
dispatch(showSuccessMessage(T.translate("edit_company.company_saved")));
});
)(params)(dispatch)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Falta return — promesa no propagada al caller

La función async resuelve inmediatamente al llegar aquí porque putRequest(...)(params)(dispatch) no está retornado. El caller en handleSave ve una Promise<void> que ya está resuelta, por lo que .then() dispara antes de que la API responda.

Consecuencias: el dialog se cierra antes de confirmar éxito, la lista se recarga con datos desactualizados, y la protección de doble-submit queda sin efecto.

// ✅ agregar return en ambas ramas
return putRequest(...)(params)(dispatch)
  .then(...)
  .finally(...);

Comment thread src/actions/company-actions.js Outdated
})
);
});
)(params)(dispatch)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] Falta return en rama else — mismo problema que la rama if

postRequest(...)(params)(dispatch) tampoco está retornado. Ver el comentario en la línea 163.

);
if (!supportingCompany) return;

const confirm = showConfirmDialog({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[HIGH] showConfirmDialog usada de forma sincrónica — el delete siempre se ejecuta

showConfirmDialog retorna una Promise<boolean>. Un objeto Promise es siempre truthy, por lo que onDeleteSponsorship se llama siempre, sin importar si el usuario confirma o cancela. El dialog de confirmación aparece pero el delete ya fue despachado.

// ❌ actual — confirm es Promise (siempre truthy)
const confirm = showConfirmDialog({ ... });
if (confirm) onDeleteSponsorship(...);

// ✅ correcto
const handleDeleteSponsorship = async (sponsorshipId) => {
  // ...
  const confirmed = await showConfirmDialog({ ... });
  if (confirmed) onDeleteSponsorship(...);
};

@smarcet smarcet dismissed their stale review June 18, 2026 14:56

Superseded by corrected review in English

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review against the popup/dialog pattern defined in ticket #86bag2zk7. The new CompanyListPage + CompanyDialog deviates from the established pattern in 4 critical points and has 2 additional bugs. See inline comments for details.

Comment thread src/pages/companies/company-list-page.js Outdated
Comment thread src/pages/companies/company-list-page.js Outdated
Comment thread src/pages/companies/company-list-page.js Outdated
Comment thread src/pages/companies/company-list-page.js Outdated
Comment thread src/actions/company-actions.js
Comment thread src/actions/company-actions.js Outdated
Comment thread src/pages/companies/components/company-dialog.js Outdated
@smarcet smarcet dismissed their stale review June 18, 2026 15:02

Superseded by updated review with only confirmed findings

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review against ticket #86bag2zk7. Four confirmed bugs and one question on an intentional behavior change. See inline comments.

Comment thread src/actions/company-actions.js
Comment thread src/actions/company-actions.js Outdated
Comment thread src/pages/companies/components/company-dialog.js Outdated
Comment thread src/pages/companies/company-list-page.js Outdated
Comment thread src/actions/company-actions.js
Comment thread src/components/mui/formik-inputs/mui-formik-color-input.js Outdated

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@tomrndom please review comments

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants